package com.xuanyue.db.xuan.core.db;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

import com.xuanyue.db.xuan.core.exception.SQLException;
import com.xuanyue.db.xuan.core.index.BooleanIndex;
import com.xuanyue.db.xuan.core.index.DateIndex;
import com.xuanyue.db.xuan.core.index.FLOATIndex;
import com.xuanyue.db.xuan.core.index.IntIndex;
import com.xuanyue.db.xuan.core.index.ListObject;
import com.xuanyue.db.xuan.core.index.LongIndex;
import com.xuanyue.db.xuan.core.index.MapIndex;
import com.xuanyue.db.xuan.core.index.UFLOATIndex;
import com.xuanyue.db.xuan.core.index.UIntIndex;
import com.xuanyue.db.xuan.core.index.ULongIndex;
import com.xuanyue.db.xuan.core.table.IColumn;
import com.xuanyue.db.xuan.core.table.IXyTable;
import com.xuanyue.db.xuan.core.table.XyTable;
import com.xuanyue.db.xuan.core.tools.Savor;
import com.xuanyue.db.xuan.utils.StringUtils;

public class XyDBV2 implements IXyDBV2{

	private DBMetaV2 meta;
	private String dataPath;
	private Map<String,IXyTable> name2table = new HashMap<>();
	private ReentrantReadWriteLock metaLock = new ReentrantReadWriteLock();
	
	@Override
	public DBMetaV2 getDBMetaV2() {
		return meta;
	}
	@Override
	public void init(String dataPath) {
		DBMetaV2 meta = loadMeta(dataPath);
		if(meta==null) {
			meta = new DBMetaV2();
			meta.setDataPath(dataPath);
		}
		init(meta);
	}
	@Override
	public void init(DBMetaV2 dbMeta) {
		this.meta = dbMeta;
		this.dataPath = dbMeta.getDataPath();
		Map<String,TableMetaV2> tables = dbMeta.getTables();
		tables.forEach( (k,v )->{
			name2table.put(k, makeTable(v));
		});

		for(Entry<String,IXyTable> en:name2table.entrySet()) {
			en.getValue().init();
		}
	}

	/**
	 * 根据元数据创建表对象
	 * @param tm
	 * @return
	 * @author 解观海
	 * @date 2021年1月12日
	 */
	private IXyTable makeTable(TableMetaV2 tm) {
		IXyTable table = new XyTable(tm.getSource(),tm.getName(),String.format("%s/%s", dataPath,tm.getName()));
		
		tm.getColumns().forEach( (k,v)->{
			table.addColumn(k, getColumnFromMeta(v.getType()));
		});
		
		return table;
	}
	
	@SuppressWarnings({ "unchecked" })
	private static <T>T getValue(Map<String,Object> base,String key){
		return (T)base.get(key);
	}
	
	
	
	private synchronized DBMetaV2 loadMeta(String dataPath) {
		File m = new File(String.format("%s/_meta_", dataPath));
		if(m.exists()) {
			return Savor.read(String.format("%s/_meta_", dataPath) );
		}else {
			return null;
		}
			
	}
	private synchronized void saveMeta() {
		if(this.meta!=null) {
			Savor.write(this.meta, String.format("%s/_meta_", this.dataPath));
		}
	}
	public static IColumn getColumnFromMeta(Map<String,Object> type) {
		String className = type.get("typeName").toString();
		if("IntIndex".equals(className)) {
			return new IntIndex(getValue(type,"max"));
		}else if("UIntIndex".equals(className)) {
			return new UIntIndex(getValue(type,"max"));
		}else if("LongIndex".equals(className)) {
			if(type.containsKey("zero")) {
				return new LongIndex(getValue(type,"size"),getValue(type,"zero"));
			}else {
				return new LongIndex(getValue(type,"size"));
			}
		}else if("ULongIndex".equals(className)) {
			return new ULongIndex(getValue(type,"max"));
		}else if("FLOATIndex".equals(className)) {
			if(type.containsKey("zero")) {
				return new FLOATIndex(getValue(type,"size"),getValue(type,"eNum"),getValue(type,"zero"));
			}else {
				return new FLOATIndex(getValue(type,"size"),getValue(type,"eNum"));
			}
		}else if("UFLOATIndex".equals(className)) {
			return new UFLOATIndex(getValue(type,"size"),getValue(type,"eNum"));
		}else if("DateIndex".equals(className)) {
			return new DateIndex();
		}else if("BooleanIndex".equals(className)) {
			return new BooleanIndex();
		}else if("ListObject".equals(className)) {
			int num = getValue(type,"num");
			Map<String,Object> eleType = getValue(type,"eleType");
			List<IColumn> list = new ArrayList<>();
			for(int i=0;i<num;i++) {
				list.add( getColumnFromMeta( eleType));
			}
			return new ListObject<Object>(list);
		}else if("MapIndex".equals(className)) {
			Map<String,Object> eleType = getValue(type,"eleType");
			MapIndex m = new MapIndex();
			List<String> keys = getValue(type,"keys");
			for(String key:keys) {
				m.addColumn(key,  getColumnFromMeta( eleType) );
			}
			return m;
		}else {
			throw new SQLException("the type "+className+" is not support");
		}
	}
	
	@Override
	public void setSource(String name,int source) {
		this.meta.getTables().get(name).setSource(source);
		this.getTable(name).setSource(source);
		saveMeta();
	}
	@Override
	public void dropColumn(String name,String cname) {
		this.meta.getTables().get(name).getColumns().remove(cname);
		this.getTable(name).dropColumn(cname);
		saveMeta();
	}
	@Override
	public void addColumn(String name,ColumnMetaV2 cmV2) {
		this.meta.getTables().get(name).getColumns().put(cmV2.getName(), cmV2);
		IColumn c = getColumnFromMeta(cmV2.getType());
		this.getTable(name).addColumn(name, c);
		c.init();
		saveMeta();
	}
	@Override
	public boolean alertColumn(String tableName,String con,ColumnMetaV2 cmV2) {
		
		ColumnMetaV2 old = this.meta.getTables().get(tableName).getColumns().get(con);
		if(!old.check(cmV2)) {
			return false;
		}
		this.meta.getTables().get(tableName).getColumns().remove(con);//移除原始列
		this.meta.getTables().get(tableName).getColumns().put(cmV2.getName(), cmV2);
		
		IColumn c = getColumnFromMeta(cmV2.getType());
		c.setPath(String.format("%s/%s/%s", this.dataPath,tableName,cmV2.getName()));
		IColumn o = this.getTable(tableName).getColumn(con);
		if(!StringUtils.equeals(con, cmV2.getName())) {
			
			o.setPath(String.format("%s/%s/%s", this.dataPath,tableName,cmV2.getName()));
			new File(String.format("%s/%s/%s", this.dataPath,tableName,con)).renameTo(
					new File(String.format("%s/%s/%s", this.dataPath,tableName,cmV2.getName()))
					);
			this.getTable(tableName).getName2column().remove(con);
		}
		
		o.modify(c,true);
		this.getTable(tableName).addColumn(cmV2.getName(), o);
		saveMeta();
		return true;
	}
	
	private XyTable table(TableMetaV2 tableMetaV2) {
		String tname = tableMetaV2.getName();
		XyTable table = new XyTable(tableMetaV2.getSource(),tname,String.format("%s/%s", dataPath,tname));
		for(Entry<String,ColumnMetaV2> en:tableMetaV2.getColumns().entrySet()) {
			table.addColumn(en.getKey(), getColumnFromMeta(en.getValue().getType()));
		}
		table.init();
		
		return table;
	}
	
	@Override
	public IXyTable createTable(TableMetaV2 tableMetaV2) {
		ColumnMetaV2 columnMetaV2 = new ColumnMetaV2();
		columnMetaV2.setName(IXyTable.VERSION_COLUMN);
		Map<String,Object> type = new HashMap<>();
		type.put("typeName", "ULongIndex");
		type.put("max", 40);
		columnMetaV2.setType(type);
		
		tableMetaV2.add(columnMetaV2);
		
		XyTable table = table(tableMetaV2);
		
		this.meta.addTable(tableMetaV2);
		name2table.put(tableMetaV2.getName(), table);
		saveMeta();
		return table;
	}
	
	
	@Override
	public void load() {
		for(Entry<String,IXyTable> en:name2table.entrySet()) {
			en.getValue().load();
		}
	}

	
	
	@Override
	public IXyTable getTable(String name) {
		return name2table.get(name);
	}

	@Override
	public ReadLock getReadLockOfMeta() {
		return metaLock.readLock();
	}
	@Override
	public WriteLock getWriteLockOfMeta() {
		return metaLock.writeLock();
	}

}
